package com.dynamic.mybatis.core.parser.field;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.dynamic.mybatis.core.enums.CommandType;
import com.dynamic.mybatis.core.enums.ReturnType;
import com.dynamic.mybatis.core.enums.Type;
import com.dynamic.mybatis.core.metadata.*;
import com.dynamic.mybatis.core.session.DynamicSqlSession;
import com.dynamic.mybatis.core.toolkit.BeanUtils;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.xmltags.XMLScriptBuilder;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.w3c.dom.NodeList;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author xs
 * @Date 2022/11/17 11:05
 */
public abstract class DynamicMappedStatementInputParameterFieldParser extends DynamicMappedStatementOutputParameterFieldParser {

    private static Map<String,Method> nodeHandlerMethodMap = new HashMap<>();
    private static Map<String,Object> nodeHandlerMap;
    List<Pattern> patterns = new ArrayList<>();


    public DynamicMappedStatementInputParameterFieldParser(DynamicSqlSession dynamicSqlSession) {
        super(dynamicSqlSession);
        patterns.add(Pattern.compile("#\\{[\\S]*\\}|\\$\\{[\\S]*\\}"));
        try {
            XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(getConfiguration(),null);
            Field field =  XMLScriptBuilder.class.getDeclaredField("nodeHandlerMap");
            field.setAccessible(true);
            nodeHandlerMap = (Map<String, Object>) field.get(xmlScriptBuilder);
            for(Map.Entry<String,Object> entry : nodeHandlerMap.entrySet()){
                Method method = entry.getValue().getClass().getMethod("handleNode",XNode.class,List.class);
                method.setAccessible(true);
                nodeHandlerMethodMap.put(entry.getKey(),method);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }




    public String getFirstPrimaryKeyPath(MappedStatementMetaField field){
        if(field.isPrimaryKey() || "id".equals(field.getField())){
            return field.getField();
        }
        if(CollectionUtils.isEmpty(field.getChildren())){
            return "";
        }
        StringBuilder path = new StringBuilder();
        if(StringUtils.isNotBlank(field.getField())){
            path.append(field.getField());
        }
        for(MappedStatementMetaField f : field.getChildren()){
            String   firstFieldKey = getFirstPrimaryKeyPath(f);
            if(StringUtils.isNotBlank(firstFieldKey)){
                if(path.length() > 0){
                    path.append(".");
                }
                 path.append(firstFieldKey);
                return path.toString();
            }
        }
        return  path.toString();
    }




    public MappedStatementMetaField inputParameterFieldParser(List<MappedStatementMetaField> databaseFields,DynamicMappedStatement dms,boolean wrapper) throws DocumentException {
        Element element = dynamicSqlSession.getDynamicMappedStatementHelper().getMappedStatementParser().parserDom4jMapperBodyXml(dms.getSqlStatement(),dms.getNamespace());
     //  XNode xNodes =  mappedStatementParser.parserMapperBodyXml(mappedStatement.getMappedStatement(),mappedStatement.getNamespace());
       // List<DynamicSqlNode> sqlNodeTree =  this.parseSqlNodeTree(xNodes,false,"","");
        List<DynamicSqlNode> sqlNodeTree =  this.parseSqlNodeTreeByDom4j(element,false,"","");
        sqlNodeTree = this.filterEmptyPathNode(sqlNodeTree);
        List<ParameterPath> paths = this.getParameterPaths(sqlNodeTree);
        if(StringUtils.isNotBlank(dms.getParameterWrapper()) && wrapper){
            if(dms.isBatchExecute()){
                paths = paths.stream().peek(p -> p.path = dms.getParameterWrapper()+"[]."+p.path).collect(Collectors.toList());
            }else {
                paths = paths.stream().peek(p -> p.path = dms.getParameterWrapper()+"."+p.path).collect(Collectors.toList());
            }
        }
        MappedStatementMetaField field =  this.buildMappedStatementField(paths);
        field = formatMappedStatementParameterField(field,dms,wrapper);
        //建数据库字段合并到参数集
        mergeDataBseToJavaBatch(databaseFields,field.getChildren(),dms.getCommandType() == CommandType.StandardCommand.SELECT);
        return field;
    }

    //特殊结果集处理
    public MappedStatementMetaField formatMappedStatementParameterField(MappedStatementMetaField field, DynamicMappedStatement dms,boolean wrapper){

        if(dms.isBatchExecute() && StringUtils.isBlank(dms.getParameterWrapper())){
            field.setType(Type.StandardType.ARRAY.type);
        }
        //合并去重
        field = distinctMergeChildrenTree(field);
        if(dms.getReturnType() == ReturnType.StandardReturnType.PAGE){
            MappedStatementMetaField pageNumberField = new MappedStatementMetaField();
            pageNumberField.setType(Type.StandardType.BIGINT.type);
            pageNumberField.setName("当前页");
            pageNumberField.setField(DynamicPage.PAGE_NUMBER);
            pageNumberField.setVisible(Boolean.TRUE);
            pageNumberField.setDefaultValue(1);
            MappedStatementFieldValidator pageNumberFieldValidator = new MappedStatementFieldValidator();
            pageNumberFieldValidator.setMin(1);

            pageNumberFieldValidator.setUnsigned(Boolean.TRUE);
            pageNumberField.setValidator(pageNumberFieldValidator);
            field.getChildren().add(pageNumberField);

            MappedStatementMetaField pageSizeField = new MappedStatementMetaField();
            pageSizeField.setType(Type.StandardType.BIGINT.type);
            pageSizeField.setName("每页数量");
            pageSizeField.setField(DynamicPage.PAGE_SIZE);
            pageSizeField.setVisible(Boolean.TRUE);
            pageSizeField.setDefaultValue(10);
            MappedStatementFieldValidator pageSizeFieldValidator = new MappedStatementFieldValidator();
            pageSizeFieldValidator.setMin(1);

            pageSizeFieldValidator.setUnsigned(Boolean.TRUE);
            pageSizeField.setValidator(pageSizeFieldValidator);
            field.getChildren().add(pageSizeField);
        }
        // MappedStatement mappedStatement = configuration.getMappedStatement(dms.getDynamicMappedStatementId());
         //  mappedStatement.getParameterMap();
        // mappedStatementField.setName(dms.getDynamicMappedStatementId());
        if(StringUtils.isBlank(field.getType())){
            field.setType(Type.StandardType.STRUCT.type);
        }else if(Type.StandardType.ARRAY.type.equals(field.getType())){
            field.setGenericType(Type.StandardType.STRUCT.type);
        }
        if(StringUtils.isNotBlank(dms.getParameterWrapper()) && dms.isBatchExecute() && wrapper
                && dms.getParameterWrapper().split("\\.").length == 1 && Type.StandardType.ARRAY.type.equalsIgnoreCase(field.getChildren().get(0).getType())){
            field.getChildren().get(0).setGenericType(Type.StandardType.STRUCT.type);
        }
         return  field;
    }


    private List<String> matcherParameter(String text){
        List<String> paths = new ArrayList<>();
        for (Pattern pattern : patterns){
            Matcher matcher = pattern.matcher(text);
            while (matcher.find()){
                String p =  matcher.group();
                String param =   p.substring(2,p.length()-1);
                paths.add(param);
            }
        }
        return paths;
    }

   private List<String> matcherQuoteReplace(String text,String key,String alias){
       List<String> paths = matcherParameter(text);
       if(CollectionUtils.isEmpty(paths) || StringUtils.isBlank(key) || StringUtils.isBlank(alias)){
           return paths;
       }
       for(int i = 0 ; i < paths.size() ; i++){
           String path = paths.get(i).replaceAll("\\s","");
           if(path.equals(alias) || path.startsWith(alias+".")){
               path = path.replaceFirst(alias,key+"[]");
           }
           paths.set(i,path);
       }
       return paths;
   }


   //解析引用的标签   foreach  bind
    private  List<DynamicSqlNode> parseSqlNodeTree(XNode rootXNode,boolean parentIf,String key,String alias){
        List<DynamicSqlNode> dynamicSqlNodes = new ArrayList<>();
        List<XNode> children1 = rootXNode.getChildren();
        if(CollectionUtils.isEmpty(children1)){
            children1 = new ArrayList<>();
            children1.add(rootXNode);
        }
        for(XNode xNode : children1){
            NodeList children = xNode.getNode().getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                    XNode newXNode =  xNode.newXNode(children.item(i));
                    String body = newXNode.getStringBody("").replaceAll("^\\s*|\\s*$","");
                    DynamicSqlNode dynamicSqlNode = new DynamicSqlNode();
                    if(newXNode.getNode().hasChildNodes()){
                        dynamicSqlNode.setChildren(parseSqlNodeTree(newXNode,parentIf,key,alias));
                    }else if (StringUtils.isNotBlank(body)){
                        if("foreach".equals(xNode.getName())){
                            key = xNode.getStringAttribute("collection");
                            alias = xNode.getStringAttribute("item");
                        }
                        parentIf = parentIf ? true : "if".equals(xNode.getName()) || "when".equals(xNode.getName());
                        dynamicSqlNode.setParentIf(parentIf);
                        dynamicSqlNode.setSqlNode(newXNode.getName());
                        String text = newXNode.getStringBody("");
                        List<String> paths =  matcherQuoteReplace(text,key,alias);
                        dynamicSqlNode.setPaths(paths);
                    }
                  dynamicSqlNodes.add(dynamicSqlNode);
                }
            }
        return dynamicSqlNodes;
    }

    //解析引用的标签   foreach  bind
    private  List<DynamicSqlNode> parseSqlNodeTreeByDom4j(Element rootElement,boolean parentIf,String key,String alias){
       String tagXml  = rootElement.asXML();
        List<DynamicSqlNode> dynamicSqlNodes = new ArrayList<>();
        List<Element> children = rootElement.elements();
        if(CollectionUtils.isEmpty(children)){
            children = new ArrayList<>();
            children.add(rootElement);
        }
        for(Element element : children){
                String tagName = element.getName();
                String body = element.getText().replaceAll("^\\s*|\\s*$","");
                DynamicSqlNode dynamicSqlNode = new DynamicSqlNode();
                if(element.elements() != null && element.elements().size() > 0){
                    dynamicSqlNode.setChildren(parseSqlNodeTreeByDom4j(element,parentIf,key,alias));
                }
                if (StringUtils.isNotBlank(body)){
                    if("foreach".equals(element.getName())){
                        key = element.attributeValue("collection");
                        alias = element.attributeValue("item");
                    }
                    parentIf = parentIf ? true : "if".equals(element.getName()) || "when".equals(element.getName());
                    dynamicSqlNode.setParentIf(parentIf);
                    dynamicSqlNode.setSqlNode(element.getName());
                    String text = element.getText();
                    List<String> paths =  matcherQuoteReplace(text,key,alias);
                    dynamicSqlNode.setPaths(paths);
                }
                dynamicSqlNodes.add(dynamicSqlNode);
            }
        return dynamicSqlNodes;
    }


    //将没有入参的子节点丢弃
    private List<DynamicSqlNode> filterEmptyPathNode(List<DynamicSqlNode> dynamicSqlNodes){
        List<DynamicSqlNode> dsnList = new ArrayList<>();
        if(CollectionUtils.isEmpty(dynamicSqlNodes)){ return  dsnList;}
        for(DynamicSqlNode dsn : dynamicSqlNodes){
            if(CollectionUtils.isNotEmpty(dsn.getPaths())){
                dsnList.add(dsn);
            }else {
                dsnList.addAll(filterEmptyPathNode(dsn.getChildren()));
            }
        }
        return dsnList;
    }



    private List<ParameterPath> getParameterPaths(List<DynamicSqlNode> tree){
        List<DynamicSqlNode> array = toArray(tree);
        List<ParameterPath> paths = new ArrayList<>();
        for(DynamicSqlNode dsn : array){
            if (CollectionUtils.isNotEmpty(dsn.getPaths())) {
                paths.addAll(dsn.getPaths().stream().map(s -> new ParameterPath(s.split(",")[0],!dsn.isParentIf())).collect(Collectors.toList()));
            }
        }
        if(CollectionUtils.isEmpty(paths) || paths.size() <= 1){
            return  paths;
        }
        //去重
        for(int i = 0 ; i < paths.size() ; i++){
            for(int j = i+1 ;  j < paths.size()-1 ;j++){
                String before = paths.get(i).getPath();
                String after = paths.get(j).getPath();
                if(StringUtils.isNotBlank(before) && ( before.startsWith(after+".") || before.equals(after))){
                    paths.get(i).required = (paths.get(i).required || paths.get(j).required);
                    paths.set(j,ParameterPath.empty);
                }
            }
        }
        return paths.stream().distinct().filter( p -> p != ParameterPath.empty ).collect(Collectors.toList());
    }

    private List<DynamicSqlNode> toArray(List<DynamicSqlNode> tree){
        List<DynamicSqlNode> array = new ArrayList<>();
        for(DynamicSqlNode dsn : tree){
            array.add(dsn);
            if(CollectionUtils.isNotEmpty(dsn.getChildren())){
                array.addAll(toArray(dsn.getChildren()));
            }
        }
        return array;
    }

    private MappedStatementMetaField buildMappedStatementField(List<ParameterPath> paths){
        MappedStatementMetaField field = new MappedStatementMetaField();
        List<MappedStatementMetaField> children = new ArrayList<>();
        field.setChildren(children);
        for(ParameterPath pp : paths){
            String path = pp.getPath();

            String[] array = path.split("\\.");
            Type firstType = array.length > 1 ? Type.StandardType.STRUCT : null;
            MappedStatementMetaField secondField  = batchBuildMappedStatementField(new String[]{array[0]},firstType,pp.required).get(0);
            children.add(secondField);
            Type netType = array.length > 2 ? Type.StandardType.STRUCT : null;
            secondField.setChildren(batchBuildMappedStatementField(Arrays.copyOfRange(array,1,array.length),netType,pp.required));
        }
        return field;
    }


    private List<MappedStatementMetaField> batchBuildMappedStatementField(String[]  paths,Type type,boolean required){
        if(paths == null || paths.length < 1){
            return null;
        }
        List<MappedStatementMetaField> fields = new ArrayList<>();
        MappedStatementMetaField field = new MappedStatementMetaField();
        String key = paths[0];
        if(key.endsWith("[]")){
            field.setType(Type.StandardType.ARRAY.name().toLowerCase());
            key = key.substring(0,key.length()-2);
        }else if(type != null){
            field.setType(type.getType());
        }
        field.setName(key);
        field.setField(key);
        field.setVisible(Boolean.TRUE);
        MappedStatementFieldValidator validator = new MappedStatementFieldValidator();
        validator.setRequired(required);
        field.setValidator(validator);
        fields.add(field);
        if(paths.length > 1){
            if(!field.getType().equalsIgnoreCase(Type.StandardType.STRUCT.type)){
                field.setGenericType(Type.StandardType.STRUCT.name().toLowerCase());
            }
            Type netType = paths.length > 2 ? Type.StandardType.STRUCT : null;
            field.setChildren(batchBuildMappedStatementField(Arrays.copyOfRange(paths,1,paths.length),netType,required));
        }
        return fields;
    }

    static class ParameterPath{

      public ParameterPath(String path, boolean required) {
          this.path = path;
          this.required = required;
      }
      static ParameterPath empty = new ParameterPath("",false);
      String path;
        boolean required;
      public String getPath() {
          return path;
      }

      public boolean isRequired() {
          return required;
      }
  }




    public   MappedStatementMetaField distinctMergeChildrenTree(MappedStatementMetaField field){
        if(field.getChildren() == null || field.getChildren().size() < 1){
            return  field;
        }
        MappedStatementMetaField first = field.getChildren().get(0) ;
        for(int i = 1 ; i < field.getChildren().size() ; i++){
            mergeChildrenTree(first,field.getChildren().get(i));
        }
        //去重合并
        List<MappedStatementMetaField> children = field.getChildren().stream().collect(
                Collectors.collectingAndThen(Collectors.toCollection(() ->
                        new TreeSet<>(Comparator.comparing(f -> f.getField()))), ArrayList::new));
        field.setChildren(children);
        return field;
    }



    public  void  mergeChildrenTree(MappedStatementMetaField master, MappedStatementMetaField slave){
        if(slave.getChildren() == null){
            return;
        }
        for(MappedStatementMetaField slaveChild : slave.getChildren()){
            if(master.getChildren() == null){ master.setChildren(new ArrayList<>());}
            Optional<MappedStatementMetaField> masterSameChild = master.getChildren().stream().filter(item -> slaveChild.getField().equals(item.getField())).findFirst();
            if (masterSameChild.isPresent()) {
                mergeChildrenTree(masterSameChild.get(),slaveChild);
            }else {//不存在就拼接
                master.getChildren().add(slaveChild);
            }
        }
    }

    public  void  mergeTree(MappedStatementMetaField master, MappedStatementMetaField slave){
        if(slave == null){
            return ;
        }else if(CollectionUtils.isEmpty(slave.getChildren())){
            if(master.getChildren() == null){ master.setChildren(new ArrayList<>());}
            if(!Objects.equals(master.getField(),slave.getField()) || (master.getField() == null && !Objects.equals(master.getType(),slave.getType()))){
                Optional<MappedStatementMetaField> sameOptional = master.getChildren().stream().filter(m-> Objects.equals(m.getField(),slave.getField())).findFirst();
                if(sameOptional.isPresent()){
                    MappedStatementMetaField same = sameOptional.get();
                    BeanUtils.copy(slave,same);
                    MappedStatementFieldValidator validator;
                    if(same.getValidator() != null){
                        validator = BeanUtils.copy(slave.getValidator(),same.getValidator());
                    }else {
                        validator = slave.getValidator();
                    }
                    same.setValidator(validator);
                }else {
                    master.getChildren().add(slave);
                }
            }else if(Objects.equals(master.getField(),slave.getField()) && Objects.equals(master.getType(),slave.getType())){
                BeanUtils.copy(slave,master);
            }
            return;
        }
        for(MappedStatementMetaField slaveChild : slave.getChildren()){
            List<MappedStatementMetaField> masterChildren = master.getChildren();
            BeanUtils.copy(slave,master);
            master.setChildren(masterChildren);
            if(master.getChildren() == null){ master.setChildren(new ArrayList<>());}
            Optional<MappedStatementMetaField> masterSameChild = master.getChildren().stream().filter(item -> Objects.equals(slaveChild.getField(),item.getField())).findFirst();
            if (masterSameChild.isPresent()) {
                mergeTree(masterSameChild.get(),slaveChild);
            }else {//不存在就拼接
                master.getChildren().add(slaveChild);
            }
        }
    }


    public  List<MappedStatementMetaField>  mergeArray(List<MappedStatementMetaField> master, List<MappedStatementMetaField> slave){
       for(MappedStatementMetaField masterField : master){
           MappedStatementMetaField slaveField =  slave.stream().filter(f-> Objects.equals(masterField.getField(),f.getField())).findFirst().orElse(null);
           if(slaveField == null){
               continue;
           }
           MappedStatementFieldValidator validator;
           if(masterField.getValidator() != null){
               validator = BeanUtils.copy(slaveField.getValidator(),masterField.getValidator());
           }else {
               validator = slaveField.getValidator();
           }
           BeanUtils.copy(slaveField,masterField);
           masterField.setValidator(validator);
       }
       return master;
    }

}